import torch
import torch.nn as nn
import torch.nn.functional as F


class MPN(nn.Module):

    def __init__(self, config):
        super(MPN, self).__init__()
        self.n_channels = 1  # config.n_channels
        self.n_classes = 1  # config.n_classes
        self.bilinear = False  # config.bilinear

        self.inc = DoubleConv(self.n_channels, 64)
        self.down1 = Down(65, 128)  # Down是先pooling后卷积
        self.down2 = Down(129, 256)  # 因为要concat上mask
        self.down3 = Down(257, 512)  # 最下面的一层

        self.up0 = Up(512, 256, self.bilinear)  # forward第一个参数的channel数是257 第二个是二分之一
        self.up1 = Up(256, 128, self.bilinear)
        self.up2 = Up(128, 64, self.bilinear)
        self.outc = OutConv(64, self.n_classes)

        self.avgpooling0 = nn.AvgPool2d(2)
        self.avgpooling1 = nn.AvgPool2d(2)
        self.avgpooling2 = nn.AvgPool2d(2)

    def forward(self, x, mask0):
        x0 = self.inc(x)
        # mask0 = self.avgpooling0(mask)
        x0_concat = torch.concat([x0, mask0], dim=1)  # 65 256 256

        x1 = self.down1(x0_concat)  # 128 128 128
        mask1 = self.avgpooling0(mask0)  # 1 128 128
        x1_concat = torch.concat([x1, mask1], dim=1)  # 129 128 128

        x2 = self.down2(x1_concat)  # 256 64 64
        mask2 = self.avgpooling1(mask1)  # 1 64 64
        x2_concat = torch.concat([x2, mask2], dim=1)  # 257 64 64  # channel number is 257

        x3 = self.down3(x2_concat)  # 512 32 32 # 最下面的一层 不需要跳跃连接
        # mask3 = self.avgpooling1(mask2)  # 1 32 32
        # x3_concat = torch.concat([x3, mask3], dim=1)  # 513 32 32

        x4 = self.up0(x3, x2_concat)  # 128 64 64
        x5 = self.up1(x4, x1_concat)
        x6 = self.up2(x5, x0_concat)
        x7 = self.outc(x6)

        return x7


class DoubleConv(nn.Module):
    """(convolution => [BN] => ReLU) * 2"""
    """
    2022年9月19日19:50:48
    嵇老板说 在风格迁移中 InstanceNormalization用的比较多，所以先把BN换成IN
    """

    def __init__(self, in_channels, out_channels, mid_channels=None):
        super(DoubleConv, self).__init__()
        if not mid_channels:
            mid_channels = out_channels
        self.double_conv = nn.Sequential(
            nn.Conv2d(in_channels, mid_channels, kernel_size=3, padding=1, bias=False),
            nn.BatchNorm2d(mid_channels),
            # nn.InstanceNorm2d(mid_channels),
            nn.ReLU(inplace=True),
            nn.Conv2d(mid_channels, out_channels, kernel_size=3, padding=1, bias=False),
            nn.BatchNorm2d(out_channels),
            # nn.InstanceNorm2d(out_channels),
            nn.ReLU(inplace=True)
        )

    def forward(self, x):
        return self.double_conv(x)


class Down(nn.Module):
    """Downscaling with maxpool then double conv"""

    def __init__(self, in_channels, out_channels):
        super().__init__()
        self.maxpool_conv = nn.Sequential(
            nn.MaxPool2d(2),
            DoubleConv(in_channels, out_channels)
        )

    def forward(self, x):
        return self.maxpool_conv(x)


class Up(nn.Module):
    """Upscaling then double conv"""

    def __init__(self, in_channels, out_channels, bilinear=True):
        super().__init__()

        # if bilinear, use the normal convolutions to reduce the number of channels
        if bilinear:
            self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
            self.conv = DoubleConv(in_channels, out_channels, in_channels // 2)
        else:
            self.up = nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=2, stride=2)
            # self.conv = DoubleConv(in_channels, out_channels)
            self.conv = DoubleConv(in_channels + 1, out_channels)

    def forward(self, x1, x2):
        x1 = self.up(x1)
        # input is CHW
        diffY = x2.size()[2] - x1.size()[2]
        diffX = x2.size()[3] - x1.size()[3]

        x1 = F.pad(x1, [diffX // 2, diffX - diffX // 2,
                        diffY // 2, diffY - diffY // 2])
        # if you have padding issues, see
        # https://github.com/HaiyongJiang/U-Net-Pytorch-Unstructured-Buggy/commit/0e854509c2cea854e247a9c615f175f76fbb2e3a
        # https://github.com/xiaopeng-liao/Pytorch-UNet/commit/8ebac70e633bac59fc22bb5195e513d5832fb3bd
        x = torch.cat([x2, x1], dim=1)
        return self.conv(x)


class OutConv(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(OutConv, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1)

    def forward(self, x):
        tmp = self.conv(x)
        return tmp
